Skip to content

feat(zerogit): auto-create a conventional branch before push/pr on default branch - #671

Open
euxaristia wants to merge 31 commits into
Gitlawb:mainfrom
euxaristia:euxaristia/auto-branch-naming-for-changes-workflow
Open

feat(zerogit): auto-create a conventional branch before push/pr on default branch#671
euxaristia wants to merge 31 commits into
Gitlawb:mainfrom
euxaristia:euxaristia/auto-branch-naming-for-changes-workflow

Conversation

@euxaristia

@euxaristia euxaristia commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • zero changes push and zero changes pr currently refuse to push straight to the default branch (main/master) but don't offer any alternative, so hitting that guard is a dead end.
  • Added CreateBranch, IsDefaultBranch, CurrentGitUser, SlugifyBranchComponent, and BuildBranchName to internal/zerogit.
  • push/pr now call a new ensureFeatureBranch step: if the current branch is the default branch and neither --yes nor --dry-run was passed, it generates a short slug for the diff (via the configured LLM provider, falling back to a deterministic slug derived from the changed files if no provider is configured) and checks out <git user>/<slug> before pushing. --yes and --dry-run bypass this entirely, preserving the existing refuse/preview behavior.

Linked issue

None. This came out of a direct discussion about zero having no defined branch-naming convention, not a filed issue.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./... (all green except a pre-existing, unrelated failure in internal/contextreport that also fails on unmodified main)
  • gofmt -l clean on all changed files
  • New unit tests in internal/zerogit/zerogit_test.go (CreateBranch, IsDefaultBranch, CurrentGitUser, SlugifyBranchComponent, BuildBranchName)
  • New CLI-level tests in internal/cli/workflow_test.go covering ensureFeatureBranch directly (default-branch creation with/without a provider, skip when already off default, skip on --yes/--dry-run) and end-to-end through zero changes push

Summary by CodeRabbit

  • New Features
    • changes commit, changes push, and changes pr support automatic feature-branch creation.
    • Branches receive deterministic or AI-assisted names, with safe default-branch restoration and rollback.
    • Pushes verify remote branches and configure the expected upstream.
    • Automatic workflows support dry-run and improved validation.
  • Bug Fixes
    • Remote and default-branch checks fail safely when verification is unavailable.
    • New branches are protected from accidental overwrites.
    • PR creation is blocked for unborn remotes.
    • Format-on-write preserves content when no formatter is available.
  • Tests
    • Expanded coverage for branching, remotes, rollback, naming, formatting, and edge cases.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds fail-closed Git branch detection and creation APIs. The CLI now selects feature branches for changes push and changes pr, validates remote state, restores default branches, and rolls back failed restoration. Tests cover naming, remote handling, push leases, PR preflight, and bare-remote workflows.

Changes

Automatic feature branch routing

Layer / File(s) Summary
Git branch and push APIs
internal/zerogit/zerogit.go, internal/zerogit/zerogit_test.go
Adds fail-closed default-branch detection, collision-aware branch creation, remote and upstream checks, branch reference maintenance, naming helpers, protected push behavior, and tests.
CLI Git dependency wiring
internal/cli/app.go
Adds injectable Git operations and fills them from zerogit by default.
Feature branch generation
internal/cli/workflows.go, internal/cli/workflow_test.go
Adds feature-branch preflight, tracking refresh, ahead-count checks, deterministic or LLM-generated slugs, commit-subject fallback naming, validation, and tests.
Push and PR workflow integration
internal/cli/workflows.go, internal/cli/workflow_test.go
Passes resolved branches and remotes into push and PR flows. It adds new-remote protection, unborn-remote checks, restoration, rollback, and end-to-end coverage.

Formatter execution

Layer / File(s) Summary
Formatter executable resolution
internal/tools/format_on_write.go, internal/tools/format_on_write_test.go
Resolves the formatter executable with exec.LookPath, supplies an empty stdin stream, and preserves content when lookup fails.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant runChangesPush
  participant ensureFeatureBranch
  participant zerogit
  participant Push
  User->>runChangesPush: invoke changes push
  runChangesPush->>ensureFeatureBranch: determine branch target
  ensureFeatureBranch->>zerogit: resolve default branch and remote state
  ensureFeatureBranch->>zerogit: create computed feature branch
  runChangesPush->>Push: publish branch with new-remote protection
Loading

Suggested reviewers: gnanam1990, kunjshah95

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: automatic conventional branch creation before push or PR from the default branch.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
internal/zerogit/zerogit_test.go (2)

836-848: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

CurrentGitUser's fallback branches (OS username, literal "user") are untested.

Only the git config user.name success path is covered. The two fallback tiers (L715-718 in zerogit.go) have no coverage, at least a case where the fake runner errors/returns empty output to exercise the OS-username path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 836 - 848, Extend
TestCurrentGitUser to cover CurrentGitUser’s fallback behavior when git config
returns an error or empty output, asserting the OS-username fallback and command
invocation; also add coverage for the final literal "user" fallback when no OS
username is available.

731-792: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing coverage for CreateBranch failing to check out (e.g. branch already exists).

All three subtests exercise success/dry-run/empty-name paths; none exercise the git checkout -b failure branch (L700-702 in zerogit.go), which is exactly the scenario that occurs on a name collision. Worth adding a subtest that returns a non-nil error/non-zero exit from the checkout call and asserts the wrapped error message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 731 - 792, The
TestCreateBranch coverage should include checkout failure, such as an existing
branch name. Add a subtest alongside HappyPath and DryRunDoesNotCheckout using
fakeRunner to return a non-zero/error result for the checkout invocation, then
assert CreateBranch returns an error containing the wrapped checkout failure
message.
internal/cli/workflows.go (1)

1029-1060: 🚀 Performance & Scalability | 🔵 Trivial

Extra LLM round-trip added to every default-branch push/PR without --yes.

When on the default branch (and no --yes/dry-run), ensureFeatureBranch now performs its own StreamCompletion call to name the branch, on top of any existing commit-message-generation LLM call in the same flow. That's an additional ~60s-capped network round trip and provider cost on a very common path (any push straight from main). Worth being aware of for latency/cost budgeting, and consider whether the fallback slug is "good enough" to skip the LLM call by default (e.g., behind a flag) rather than always attempting it whenever a provider is configured.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1029 - 1060, The ensureFeatureBranch
flow should not automatically perform an LLM request on every default-branch
push or PR. Use fallbackBranchSlug(summary) by default and gate
generateAutoBranchSlug behind an explicit opt-in configuration or flag,
preserving the existing branch-generation behavior when that opt-in is enabled.
internal/cli/workflow_test.go (1)

918-980: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No CLI-level test for runChangesPR + ensureFeatureBranch integration.

Coverage is added for ensureFeatureBranch in isolation and for runChangesPush (Lines 918-980), but runChangesPR also now routes through ensureFeatureBranch and forwards the ensured branch into PushOptions.Branch (workflows.go Lines 924-928, 937). Given pr hardcodes dryRun=false unlike push, this path deserves its own targeted test (e.g. mirroring TestRunChangesPushCreatesFeatureBranchWhenOnDefault for changes pr on the default branch) to lock in the new behavior before it ships.

Want me to draft that test?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` around lines 918 - 980, The CLI tests cover
feature-branch creation for runChangesPush but not the equivalent runChangesPR
flow. Add a targeted test for changes pr on the default branch, verifying
ensureFeatureBranch creates the expected branch and that runChangesPR forwards
it through PushOptions.Branch, while preserving the existing dryRun=false
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/workflows.go`:
- Around line 1087-1116: Update generateAutoBranchSlug to normalize
collected.Text before passing it to zerogit.SlugifyBranchComponent: select the
first non-empty line, trim surrounding whitespace and quotes, then slugify that
single-line value. Preserve the existing empty-slug error handling and provider
error propagation.

In `@internal/zerogit/zerogit.go`:
- Around line 637-662: Bound the remote lookup performed by IsDefaultBranch,
especially the isDefaultBranch call that may execute git ls-remote, with a short
context timeout even when the caller supplies context.Background(). On timeout
or remote lookup failure, preserve the existing local main/master fallback so
changes push/pr do not stall.
- Around line 677-704: Update CreateBranch to handle an already-existing local
branch before treating branch creation as failed: detect whether the requested
name exists locally, check it out and return the same BranchResult when it does,
while retaining checkout -b for new branches and preserving DryRun behavior.

---

Nitpick comments:
In `@internal/cli/workflow_test.go`:
- Around line 918-980: The CLI tests cover feature-branch creation for
runChangesPush but not the equivalent runChangesPR flow. Add a targeted test for
changes pr on the default branch, verifying ensureFeatureBranch creates the
expected branch and that runChangesPR forwards it through PushOptions.Branch,
while preserving the existing dryRun=false behavior.

In `@internal/cli/workflows.go`:
- Around line 1029-1060: The ensureFeatureBranch flow should not automatically
perform an LLM request on every default-branch push or PR. Use
fallbackBranchSlug(summary) by default and gate generateAutoBranchSlug behind an
explicit opt-in configuration or flag, preserving the existing branch-generation
behavior when that opt-in is enabled.

In `@internal/zerogit/zerogit_test.go`:
- Around line 836-848: Extend TestCurrentGitUser to cover CurrentGitUser’s
fallback behavior when git config returns an error or empty output, asserting
the OS-username fallback and command invocation; also add coverage for the final
literal "user" fallback when no OS username is available.
- Around line 731-792: The TestCreateBranch coverage should include checkout
failure, such as an existing branch name. Add a subtest alongside HappyPath and
DryRunDoesNotCheckout using fakeRunner to return a non-zero/error result for the
checkout invocation, then assert CreateBranch returns an error containing the
wrapped checkout failure message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f58092c2-d708-4e9f-b966-136d674d41ae

📥 Commits

Reviewing files that changed from the base of the PR and between 80c39aa and 39cfc2e.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/cli/workflows.go
Comment thread internal/zerogit/zerogit.go Outdated
Comment thread internal/zerogit/zerogit.go
@euxaristia

Copy link
Copy Markdown
Contributor Author

Addressed the review, including the nitpicks.

Actionable:

  • generateAutoBranchSlug now takes the first non-empty, quote-trimmed line of the model's response before slugifying, instead of folding any preamble or wrapping quotes into the branch name.
  • isDefaultBranch's ls-remote lookup is now bounded by a 5s timeout, so a slow or unreachable remote can't stall push/pr; falls back to the local main/master heuristic as before.
  • CreateBranch now checks whether the target branch already exists locally and checks it out instead of failing checkout -b on the collision.

Nitpicks fixed:

  • Added coverage for CurrentGitUser's OS-username fallback tier (the third "literal user" tier isn't independently testable without adding an injection seam to os/user.Current, noted in the test comment).
  • Added coverage for CreateBranch's new existing-branch path and a genuine checkout -b failure.
  • Added a CLI-level test for changes pr + ensureFeatureBranch (there was no changes pr coverage at all before this), mirroring the existing changes push test.

One nitpick I'm leaving as-is: the extra LLM round-trip on every default-branch push without --yes. That's the intended behavior, not a bug: the LLM slug is the actual point of this feature, --yes/--dry-run already bypass it for anyone who wants to skip it, and gating it behind a further flag is new config surface for a cost/latency observation rather than a correctness issue.

go vet, go build ./..., and go test -race -count=1 ./internal/zerogit/... ./internal/cli/... are all clean.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 14, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Keep branch generation from selecting an unrelated local branch
    internal/cli/workflows.go:1042-1063, internal/zerogit/zerogit.go:709-718
    changes push normally runs after changes commit, so the working tree inspected here is clean and the fallback name is always user/changes (the provider path likewise receives an empty diff). On a later push, or whenever a low-entropy fallback/LLM name already exists locally, CreateBranch checks out that arbitrary existing ref instead of creating a branch at the current default-branch HEAD. The subsequent push/PR then publishes the stale branch while leaving the user's new commit on main. Do not treat a name collision as a retry unless its history is proven to be the intended current work; use a unique name or fail visibly, and cover the ordinary commit-then-push sequence.

  • [P1] Preserve and use the actual target remote throughout auto-branching
    internal/cli/workflows.go:866-878, internal/cli/workflows.go:924-940, internal/cli/workflows.go:1034, internal/zerogit/zerogit.go:574-585
    The preflight always checks origin, while the original push may target --remote or the current branch's configured upstream. After creating a branch, that new branch has no tracking configuration, so Push falls back to origin as well. In a fork/upstream setup this can either leave the advertised default-branch dead end intact (the target remote identifies the branch as default after the origin preflight skipped creation) or push/create the PR against origin rather than the source branch's upstream. Resolve the remote once before branching, pass it to IsDefaultBranch, and pass the same resolved remote to Push; add non-origin/upstream coverage.

  • [P1] Do not fail open when default-branch lookup times out
    internal/zerogit/zerogit.go:616-629
    The new five-second deadline applies to the existing guard in Push as well as the new preflight. If ls-remote --symref takes longer than five seconds for a repository whose default is trunk/develop, the fallback only recognizes main and master; Push then proceeds without --yes. Before this change it waited for the remote answer and preserved the confirmation guard. A lookup timeout needs to fail closed (or use a trusted local default reference), not be treated as evidence that an arbitrary branch is unprotected.

  • [P1] Do not upload a diff during ordinary push/PR without an explicit opt-in
    internal/cli/workflows.go:1048-1055
    A configured provider now causes every default-branch changes push/changes pr to send the full change diff to that provider. These commands were previously git-only; the existing LLM behavior is explicitly requested through changes commit --auto. redactChangeSummary removes secret-shaped values but intentionally retains ordinary source code, so this silently exports proprietary code (and JSON mode gives no notice). Keep the deterministic local naming path by default and require an explicit LLM opt-in before constructing this completion request.

@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed e3ec76f (plus gofmt fixup 68bcce3) for all four findings.

  • Branch collisions: CreateBranch no longer checks out an existing branch under the generated name. It picks a unique suffixed name at the current HEAD (name-2 through name-9) and fails visibly when the namespace is exhausted, so an old branch with unrelated history can no longer be published while the new commit stays behind on main. The ordinary commit-then-push sequence is also covered now: with a clean tree the fallback name comes from the HEAD commit subject instead of the constant user/changes, so repeat pushes stop colliding on the same low-entropy name in the first place. Covered by TestCreateBranch/SuffixesNameInsteadOfCheckingOutExistingBranch, FailsVisiblyWhenSuffixNamespaceExhausted, and TestEnsureFeatureBranchNamesFromHeadCommitAfterCommit.
  • Remote resolution: IsDefaultBranch now resolves the remote the same way Push does (explicit --remote, then the branch's configured upstream, then origin) and reports it; ensureFeatureBranch resolves it once against the original branch and push/pr thread it into Push, so a freshly created branch with no tracking configuration no longer silently retargets origin in fork setups. Covered by TestIsDefaultBranch/ResolvesRemoteFromBranchUpstream and TestRunChangesPushUsesResolvedRemoteForNewBranch.
  • Fail closed: main and master count as default with no network at all (the safe direction). When the remote lookup fails, the check falls back to the local refs/remotes//HEAD record, which needs no network; if that record is also missing, the check errors with guidance (git remote set-head) instead of silently downgrading to the main/master heuristic and dropping the guard for trunk/develop repositories. Covered by TestIsDefaultBranch/FallsBackToLocalRemoteHeadRecord and FailsClosedWhenDefaultBranchUnknown.
  • LLM opt-in: branch naming only calls the provider when --auto is passed to push/pr, mirroring commit --auto, and only when the diff is non-empty. A configured provider alone no longer causes the change diff to be uploaded during ordinary pushes; without the flag the name is derived from deterministic local information. Covered by TestEnsureFeatureBranchDoesNotCallProviderWithoutAuto; the help text documents the flag's new scope.

go build, go vet, and the zerogit and cli suites pass locally.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/zerogit/zerogit_test.go (1)

527-668: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for Push's new fail-closed error path.

TestPushBranchesToRemote exercises isDefault == true (RejectsDefaultBranch) and isDefault == false (HappyPath, FlagsForceAndDryRun, FallbackRemoteToOrigin), but none of the subtests drive isDefaultBranch into returning an error (remote lookup fails and no local refs/remotes/<remote>/HEAD record) to verify Push's new wrapping at Line 585-587 ("cannot verify %q is not the default/protected branch: %w; use --yes to override"). This is the actual new logic added to Push in this PR and is otherwise only exercised indirectly via IsDefaultBranch's own FailsClosedWhenDefaultBranchUnknown test, which doesn't go through Push.

✅ Suggested additional subtest
t.Run("FailsWhenDefaultBranchCannotBeVerified", func(t *testing.T) {
    root := t.TempDir()
    runner := &fakeRunner{results: []CommandResult{
        {Stdout: root + "\n"},
        {Stdout: "feat/some-feature\n"},
        {Stdout: "origin\n"},
        {ExitCode: 128, Stderr: "fatal:"}, // ls-remote fails
        {ExitCode: 1},                     // no local refs/remotes/origin/HEAD record
    }}

    _, err := Push(context.Background(), PushOptions{
        Cwd:    root,
        RunGit: runner.Run,
    })
    if err == nil || !strings.Contains(err.Error(), "use --yes to override") {
        t.Fatalf("expected fail-closed error, got %v", err)
    }
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 527 - 668, Add a
FailsWhenDefaultBranchCannotBeVerified subtest to TestPushBranchesToRemote that
makes ls-remote fail and the local refs/remotes/origin/HEAD lookup fail, then
assert Push returns an error containing “use --yes to override.” Ensure the fake
runner sequence reaches Push’s wrapped isDefaultBranch error path rather than
the existing successful or protected-branch paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/zerogit/zerogit_test.go`:
- Around line 527-668: Add a FailsWhenDefaultBranchCannotBeVerified subtest to
TestPushBranchesToRemote that makes ls-remote fail and the local
refs/remotes/origin/HEAD lookup fail, then assert Push returns an error
containing “use --yes to override.” Ensure the fake runner sequence reaches
Push’s wrapped isDefaultBranch error path rather than the existing successful or
protected-branch paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16f16bb3-6e00-490a-b235-ab360363f2d3

📥 Commits

Reviewing files that changed from the base of the PR and between 8fb4f08 and 68bcce3.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/cli/app.go
  • internal/cli/workflows.go

@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed a subtest for CodeRabbit's coverage nitpick: TestPushBranchesToRemote/FailsWhenDefaultBranchCannotBeVerified drives the remote lookup and the local refs/remotes/origin/HEAD record to fail and asserts Push itself refuses with the use --yes guidance, exercising the new fail-closed wrapping directly rather than only through IsDefaultBranch.

Vasanthdev2004
Vasanthdev2004 previously approved these changes Jul 16, 2026

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The feature is well-gated branch creation skips on --yes/--dry-run, and the LLM naming path is opt-in via --auto with the diff redacted before it leaves the machine and I like that you hardened isDefaultBranch to fail closed instead of silently downgrading to the main/master name heuristic when the remote lookup times out. Two small things worth a glance: isDefaultBranch now short-circuits on the literal names main/master before consulting the remote, so a repo whose real default is trunk would treat a local feature branch named main as the default (safe direction it only blocks a push, never permits one but slightly surprising); and if the LLM slug generation fails after printing "Generating branch name using LLM..." it silently falls back to the deterministic slug with no follow-up message, which could confuse a user waiting on the LLM. Neither blocks merge.

@euxaristia
euxaristia requested a review from jatmn July 16, 2026 15:21

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Keep an unreachable remote from clearing the default-branch guard
    internal/zerogit/zerogit.go:639-646
    A local refs/remotes/<remote>/HEAD is a cache, not evidence that a differently named branch is safe. If a server renames its default from main to trunk, the local record remains origin/HEAD -> origin/main, and ls-remote then times out or fails, pushing trunk returns false here. Both the preflight and Push repeat that result, so the command pushes the newly protected branch without --yes, despite the intended fail-closed behavior. Only use a cached match to block a branch; when it does not match after live verification fails, return the unknown-default error.

  • [P1] Honor --diff-bytes before sending a branch-name prompt to the provider
    internal/cli/workflows.go:870,928,1057,1072-1079,1115-1121
    changes push/pr accept and document --diff-bytes, but neither call threads options.maxDiffBytes into ensureFeatureBranch, which invokes Inspect with only Cwd. With --auto, the resulting unbounded summary.Diff is embedded in the provider request. A user who supplies a cap to limit proprietary source sent for LLM naming therefore uploads the complete diff; pass the option through to InspectOptions just as the commit path does.

  • [P2] Refuse an auto-branch push when there is no commit to publish
    internal/cli/workflows.go:1057-1096
    The new path branches solely from working-tree status and never establishes that HEAD is ahead of the selected default branch. On a clean, up-to-date default branch it creates and pushes user/<HEAD-subject> at exactly the default tip; with only uncommitted edits it names a branch from those edits but pushes the unchanged HEAD, leaving the edits local. changes pr then leaves that remote branch behind before GitHub rejects the empty comparison. Check that there is a publishable commit range (and report no changes otherwise) before creating or pushing the branch.

  • [P2] Do not treat an LLM preamble as the generated branch slug
    internal/cli/workflows.go:1144-1155
    The helper claims to tolerate non-compliant model responses but returns the first non-empty line verbatim. A common reply such as Here is a suggested branch name:\nadd-login-page creates user/here-is-a-suggested-branch-name; a fenced reply begins with ``` and falls back silently. Parse a valid slug line or strip the supported wrappers before slugifying, and cover preamble and fenced responses.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed fixes for the review findings:

  • Branch generation no longer treats an existing name collision as safe to reuse; it now fails visibly instead of checking out an unrelated branch.
  • The auto-branch path resolves and uses the actual target remote (not always origin) for both the default-branch check and the push.
  • Default-branch lookup now fails closed on a timeout instead of assuming main/master.
  • --diff-bytes is now honored before any diff is sent to a provider for LLM branch naming, and that upload requires explicit opt-in rather than happening on every push/PR.
  • A stale cached remote HEAD can no longer clear the default-branch push guard.
  • Auto-branch push now checks there's an actual commit to publish before creating/pushing a branch.
  • Branch-name parsing now handles LLM replies with preamble text or a fenced code block instead of taking the first line verbatim.

@euxaristia
euxaristia force-pushed the euxaristia/auto-branch-naming-for-changes-workflow branch from 5b0131b to a079996 Compare July 17, 2026 05:32

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Terminate options before passing the remote to ls-remote
    internal/zerogit/zerogit.go:630
    remote can come from --remote=<value> or branch configuration, but it is inserted before the HEAD positional argument without --. A value such as --upload-pack=/bin/echo is parsed by Git as an option; git ls-remote --symref --upload-pack=/bin/echo HEAD invokes that program (and fails with its output as a protocol error). Put -- before the remote and add a dash-prefixed-remote regression test so this preflight cannot turn a remote value into Git options.

  • [P1] Avoid colliding with branches that exist only on the target remote
    internal/zerogit/zerogit.go:756
    The suffix loop probes only refs/heads/<name> locally, then ensureFeatureBranch pushes the generated name to the resolved remote. A pre-existing remote-only <user>/<slug> (for example an old/open PR whose tip is already in main, or a branch absent after pruning) is not detected; git push -u can fast-forward it and silently append the new work to that unrelated remote branch/PR. Check the chosen names against the target remote as well, or fail before creating/pushing, and cover a remote-only collision.

  • [P1] Permit a first feature-branch push to an empty remote
    internal/zerogit/zerogit.go:640
    An empty newly created remote has neither a HEAD symref from ls-remote nor a local refs/remotes/<remote>/HEAD. After the new flow creates a non-default feature branch, Push therefore returns “default branch … unknown” and requires --yes; git remote set-head --auto cannot repair an empty remote. This regresses the first push of a safe non-default branch and conflicts with the preflight’s stated legitimate-first-push path. Distinguish an unborn remote (or otherwise allow this non-default first push) while keeping the default-branch guard fail-closed.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This round landed well — the suffix-based collision handling, threading the resolved remote through the default-branch check and the push, gating the LLM naming behind --auto, and the preamble/fence slug parsing are all what I wanted to see, and CI is green across the board. But I'm with jatmn on his latest pass, so holding approval. The one I care most about: a brand-new empty remote is now a dead end — ls-remote returns no symref, there's no local refs/remotes//HEAD, and the error's own suggested fix (git remote set-head --auto) can't work on an empty remote, so the very first push of a fresh repo needs --yes. That's the exact dead-end UX this PR exists to remove; please carve out the unborn-remote case while keeping the guard fail-closed otherwise (zerogit.go ~640-656). Second, the suffix loop only probes local refs/heads, so a stale same-name branch that exists only on the target remote (an old merged PR branch, say) gets silently fast-forwarded by push -u and inherits the new commits — probe the remote too, or fail before pushing (zerogit.go ~756). The missing -- before the remote in ls-remote predates this PR, but you rewrote that function and already did it right in Push's args, so add it while you're in there. My earlier two notes (the main/master short-circuit and the quiet LLM fallback) still don't gate.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 18, 2026
@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed aabd6f2 for the three open items: (1) isDefaultBranch terminates options with -- before the remote (dash-prefixed-remote regression test included); (2) an unborn remote — ls-remote succeeds with zero refs — now counts as proof there is no protected default, so the first feature-branch push of a fresh repo is no longer a --yes dead end, while every failure path stays fail-closed and main/master stay guarded by the name heuristic; (3) CreateBranch probes the target remote's heads once (bounded, with --) so a remote-only stale branch counts as taken in the suffix loop, and an unreachable remote fails visibly before anything is created or pushed.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found issues that need to be addressed before this is ready.

Findings

  • [P1] Do not treat a missing remote HEAD symref as proof that the remote is empty
    internal/zerogit/zerogit.go:642
    git ls-remote --symref <remote> HEAD also returns no output when a non-empty remote has a dangling or missing HEAD symref. In that state this returns false, nil, which clears the new fail-closed default-branch guard for branches such as trunk; Push/ensureFeatureBranch can then publish without --yes. Confirm that the remote has no heads before allowing the unborn-repository exception, or keep the default branch unknown and require an explicit override.

  • [P1] Refuse when the working tree cannot be represented by the branch being pushed
    internal/cli/workflows.go:1069
    This only checks whether HEAD is ahead and then names a branch from the working-tree snapshot, but CreateBranch and Push publish commits only. With an ahead commit plus additional unstaged edits, the command creates a PR named after the edits while omitting them; if origin/main is absent locally, the ignored CommitsAhead error permits the same empty-branch result with only uncommitted changes. Require a clean working tree (or explicitly commit/stage it) and fail when the publishable commit range cannot be determined, rather than silently creating a partial or empty PR.

  • [P2] Make the remote branch collision check atomic with the push
    internal/zerogit/zerogit.go:784
    The remote-head snapshot is taken before checkout, while the ordinary git push -u occurs later. Another client can create the same generated name in that window; when its ref is at the default tip, this push fast-forwards it and silently appends this work to the other branch/PR. Use a push-time “destination must not exist” condition (or retry a fresh suffix after rejection) so the collision protection cannot be bypassed by a concurrent creator.

  • [P2] Do not make ordinary feature-branch pushes depend on a fixed five-second HEAD lookup
    internal/zerogit/zerogit.go:628
    Every non-main/master Push now gives ls-remote five seconds. A reachable SSH/VPN remote whose handshake or credential negotiation exceeds that limit falls through to the fail-closed error and requires --yes before the normal push can even run; the same cap also blocks the new collision probe. Honor a caller/user deadline or otherwise avoid turning a slow, established remote into a default-branch-verification failure.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 19, 2026
@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed 130b986. This addresses the latest review, all four points.

Fixed:

  • A non-empty remote with a dangling or missing HEAD symref produced the same empty ls-remote --symref output as a genuinely unborn remote, wrongly granting the first-push exception. Added an ls-remote --heads confirmation before granting it.
  • Branch naming (including --auto LLM naming) used a working-tree status snapshot, which could describe uncommitted edits a commit-only push would never publish. Now resolves against the actual remote branch being pushed to.
  • CreateBranch's remote-collision probe and the later push weren't atomic, so a concurrent creator of the same generated name could get silently fast-forwarded. Added a force-with-lease guard requiring the branch not already exist remotely.
  • The 5s ls-remote timeout added for ensureFeatureBranch had ended up inside the shared isDefaultBranch helper, so it was also capping Push's own separate, previously unbounded check. Scoped the timeout to the intended call site only.

Nothing left open, all four points from the latest review are addressed.

Vasanthdev2004
Vasanthdev2004 previously approved these changes Jul 19, 2026

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified on the current head. All three of my asks are resolved. The unborn/empty-remote case is carved out and now confirmed with a second ls-remote --heads probe before the fail-closed guard is cleared, remote-only collisions are detected and made race-safe with a zero-value force-with-lease, and the missing -- before the remote in the ls-remote preflight is in. It also picked up jatmn's later findings (the HEAD-symref-as-empty case and the unrepresentable-working-tree one).

CI is green and CodeRabbit approved on this head. My two remaining notes (a repo whose real default is "trunk" being treated as a feature branch, and the message left dangling if LLM slug generation fails) are non-gating. Approving.

@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed 521d4ba (on top of 130b986) for the remaining item from the latest review.

From 130b986 (already on this head when the review was filed against aabd6f2):

  • Missing remote HEAD symref is no longer treated as an unborn remote; an empty ls-remote --heads confirmation is required before clearing the fail-closed guard.
  • Branch naming uses the committed range against the resolved remote branch (BaseRef), not a working-tree snapshot.
  • Concurrent remote-name races are closed with --force-with-lease=<branch>: on the push that created the branch.
  • The 5s ls-remote timeout is scoped to IsDefaultBranch / ensureFeatureBranch only; Push's own default-branch check is unbounded again.

From 521d4ba (this push):

  • ensureFeatureBranch now refuses a dirty working tree before any branch is created, so an ahead commit plus uncommitted edits cannot produce a PR that omits those edits.
  • A missing/unresolvable ahead count (never-fetched remote-tracking ref) fails closed with a fetch guidance message instead of guessing there is something to publish.

Covered by TestEnsureFeatureBranchRefusesDirtyWorkingTree and the updated TestEnsureFeatureBranchFailsWhenAheadCountUnknown. go test ./internal/zerogit/... ./internal/cli/... is green.

@euxaristia
euxaristia force-pushed the euxaristia/auto-branch-naming-for-changes-workflow branch from 521d4ba to ef8e072 Compare July 20, 2026 11:07
@euxaristia

Copy link
Copy Markdown
Contributor Author

Addressed the latest review findings:

[P1] Missing remote HEAD symref is not proof the remote is empty
isDefaultBranch only grants the unborn-remote exception after a second ls-remote --heads confirms the remote has no branches. A non-empty remote with a dangling/missing HEAD fails closed.

[P1] Refuse when the working tree cannot be represented by the branch being pushed
ensureFeatureBranch now requires a clean working tree, fails when the ahead count against the remote default cannot be determined, and names the branch from the committed range (BaseRef) rather than a working-tree snapshot.

[P2] Remote branch collision check atomic with push
Newly created feature branches push with --force-with-lease=<branch>: so a concurrent creator of the same name is rejected instead of silently fast-forwarded.

[P2] Ordinary feature-branch pushes no longer depend on a fixed 5s HEAD lookup
Removed the hard 5s timeout from IsDefaultBranch and CreateBranch. Both honor the caller context (same as Push), so a slow but reachable remote is not turned into a default-branch-verification failure. Callers that need a bound pass a deadline.

Also rebased onto current main.

Push -u can publish a remote branch then fail to write local upstream
config. Treat that as incomplete, recover with set-upstream-to, and probe
the remote before reasserting the nonexistence lease on retry. Also skip
one-word LLM acknowledgements before accepting a slug-shaped line.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

Addressed the latest review on tip 27a638f7:

  1. push -u config write race — After a successful push -u, Push verifies local upstream is remote/branch. If not, it recovers with git branch --set-upstream-to=...; if recovery fails it errors (remote published, local upstream not configured) instead of reporting full success. On retry, ensureFeatureBranch no longer infers remote nonexistence from empty local config alone: new RemoteHasBranch probes the remote, and the nonexistence lease is only kept when the remote branch is actually missing.

  2. One-word LLM acknowledgementsextractBranchSlug runs preamble classification before accepting a slug-shaped line, so Sure / Certainly do not win over the real suggestion on the next line. Inline Branch name: add-login-page still works (check is on the extracted value).

Tests: Push recovery/failure cases, real-git RemoteHasBranch after push without -u, CLI lease drop/keep paths, and slug extraction ack cases.

Replace the local-state inference chain (upstream config, a
zeroAutoBranch marker, per-remote provenance tracking) that kept
producing new collision-safety edge cases across many review rounds
with a single live remoteHasBranch check against the destination
remote: missing there gets the nonexistence lease, already there gets
a plain push, and git's own fast-forward check is the safety net if
that existing branch isn't actually ours.

Removes MarkGeneratedBranch/IsGeneratedBranch, now dead, along with
the tests that pinned the specific local-state heuristics rather than
the collision-safety property itself.
@euxaristia

Copy link
Copy Markdown
Contributor Author

Pushed a rework of the branch-collision lease logic. After 17 review rounds each finding a new edge case in the local-state inference (upstream config, the zeroAutoBranch marker, per-remote provenance), I replaced the whole heuristic chain with a single live check against the destination remote right before deciding: missing there gets the nonexistence lease (protects a concurrent creator of the same name), already there gets a plain push, and git's own fast-forward check is the safety net if that existing branch turns out not to be ours (clear non-fast-forward error instead of an attempted silent recovery).

This removes MarkGeneratedBranch/IsGeneratedBranch entirely (nothing reads the marker anymore, so nothing needs to write it) and replaces the test battery that pinned specific local-state heuristics with tests pinning the collision-safety property itself. Net -245 lines.

Everything else (unborn-remote handling, default-branch restore-after-create, --yes bypass, slug extraction) is untouched.

One real behavior change worth flagging explicitly: a manually checked-out branch now also gets the nonexistence lease on its first push, not just auto-generated ones — strictly safer, but a change in what gets protected.

Verified with go build ./..., go vet ./..., gofmt, and go test ./internal/cli/... ./internal/zerogit/... (all pass).

jatmn
jatmn previously approved these changes Aug 6, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euxaristia

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 28 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (9)
internal/cli/workflow_test.go (2)

824-824: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Four consecutive bool arguments are easy to transpose. ensureFeatureBranch(ctx, w, jsonMode, cwd, remote, allowDefaultBranch, dryRun, autoNaming, maxDiffBytes, deps) is called with four bare bools in a row at about fifteen new call sites. A swap between dryRun and autoNaming still compiles and silently exercises a different path. Consider a small options struct for the flags, or a test helper with named fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` at line 824, Update the ensureFeatureBranch
call sites in workflow tests, including the call in this test, to avoid
consecutive positional boolean arguments by introducing a named options struct
or test helper for jsonMode, allowDefaultBranch, dryRun, and autoNaming.
Preserve each test’s existing flag values while making the argument names
explicit.

2100-2120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Isolate this test from the developer's global git config. The test runs real git commit and git push with only user.name and user.email set locally. A contributor with commit.gpgsign = true, a custom core.hooksPath, init.templateDir, or a pre-commit hook in their global config gets a failing test that has nothing to do with the change. git init --bare -b main also requires git 2.28 or newer, so an older git fails instead of skipping.

Set GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to a non-existent path in runWorkflowGit and in the CLI invocation, and disable signing explicitly.

🧪 Proposed hermetic setup
 func runWorkflowGit(t *testing.T, dir string, args ...string) string {
 	t.Helper()
 	cmd := exec.Command("git", args...)
 	cmd.Dir = dir
+	cmd.Env = append(os.Environ(),
+		"GIT_CONFIG_GLOBAL="+filepath.Join(t.TempDir(), "nonexistent-gitconfig"),
+		"GIT_CONFIG_SYSTEM="+filepath.Join(t.TempDir(), "nonexistent-gitsystem"),
+		"GIT_CONFIG_NOSYSTEM=1",
+	)
 	out, err := cmd.CombinedOutput()
 	runWorkflowGit(t, repo, "config", "user.email", "zero@example.invalid")
+	runWorkflowGit(t, repo, "config", "commit.gpgsign", "false")
+	runWorkflowGit(t, repo, "config", "core.hooksPath", filepath.Join(tmp, "no-hooks"))

Note that runWithDeps runs git through the production code path, so the same environment isolation must reach it for full hermeticity.

As per coding guidelines: "Code and tests must pass on Linux, macOS, and Windows."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` around lines 2100 - 2120, Make
TestRunChangesBareRemotePushThenPRUsable hermetic by configuring
GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to nonexistent paths in runWorkflowGit
and the CLI invocation, including the runWithDeps production path. Explicitly
disable commit signing and skip the test when the installed Git does not support
git init --bare -b main, while preserving the existing test behavior.

Source: Coding guidelines

internal/cli/workflows.go (3)

527-540: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The --message check is now duplicated. Lines 527-529 fully subsume the preceding options.message != "" check, and both emit the same error text. Remove the older check to keep one rule per flag.

♻️ Proposed cleanup
-	if command != "commit" && options.message != "" {
-		return options, false, execUsageError{"--message is only valid with `zero changes commit`"}
-	}
 	if command != "commit" && options.hasMessage {
 		return options, false, execUsageError{"--message is only valid with `zero changes commit`"}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 527 - 540, Remove the redundant older
options.message != "" validation near the command-option checks, retaining the
existing `command != "commit" && options.hasMessage` rule and its error message
as the single --message validation. Leave the surrounding --auto and --dry-run
checks unchanged.

1344-1389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

firstLine and plausibleLine can never differ. Both are assigned under !isPreambleText(candidate), and the extra clause (!isPreambleText(line) || candidate != line) on Line 1378 is implied by !isPreambleText(candidate): when candidate == line the two tests are identical, and when candidate != line the first clause is already true. plausibleLine is therefore always set whenever firstLine is set, so the return firstLine on Line 1388 is unreachable except for the empty case. Collapse them into one fallback variable.

Related edge case: isPreambleText treats any line ending in ., !, or ? as preamble, so a reply of add login page. yields an empty slug and an error from generateAutoBranchSlug. Consider trimming trailing sentence punctuation before the preamble test.

♻️ Proposed simplification
 func extractBranchSlug(text string) string {
-	var firstLine string
-	var plausibleLine string
+	var fallback string
 
 	for _, line := range strings.Split(text, "\n") {
@@
 		if slugLineRe.MatchString(candidate) && !isPreambleText(candidate) {
 			return candidate
 		}
 
-		if firstLine == "" && !isPreambleText(candidate) {
-			firstLine = candidate
-		}
-
-		if (!isPreambleText(line) || candidate != line) && !isPreambleText(candidate) {
-			if plausibleLine == "" {
-				plausibleLine = candidate
-			}
-		}
+		if fallback == "" && !isPreambleText(candidate) {
+			fallback = candidate
+		}
 	}
 
-	if plausibleLine != "" {
-		return plausibleLine
-	}
-	return firstLine
+	return fallback
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1344 - 1389, In extractBranchSlug,
collapse firstLine and plausibleLine into a single fallback variable because
their current conditions are equivalent, and return that variable when no
slug-shaped candidate is found. Before applying isPreambleText, trim trailing
sentence punctuation from candidates so values such as “add login page.” remain
eligible for slug extraction and generateAutoBranchSlug does not receive an
empty result.

1265-1277: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use path.Base for git paths. summary.Files[0].Path comes from git and always uses / separators. filepath.Base applies OS-specific separator rules, so the same input can split differently on Windows. Use path.Base for a platform-independent result.

♻️ Proposed change
-		return zerogit.SlugifyBranchComponent(filepath.Base(summary.Files[0].Path))
+		return zerogit.SlugifyBranchComponent(path.Base(summary.Files[0].Path))

As per coding guidelines: "Code and tests must pass on Linux, macOS, and Windows; canonicalize paths before comparison and avoid asserting raw temporary-directory spellings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1265 - 1277, Update
fallbackBranchSlug to use path.Base instead of filepath.Base when extracting the
filename from summary.Files[0].Path, preserving platform-independent handling of
git’s slash-separated paths.

Source: Coding guidelines

internal/zerogit/zerogit.go (2)

745-752: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared remote-resolution logic.

This block duplicates the remote resolution in Push at lines 581-588. The doc comment at lines 720-724 states the two must resolve remotes identically, so any future edit must touch both sites. Extract one helper, for example resolveRemoteForBranch(ctx, runGit, root, branch, options.Remote), and call it from both functions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit.go` around lines 745 - 752, Extract the shared
remote-resolution logic into a helper such as resolveRemoteForBranch, accepting
the context, Git runner, repository root, branch, and configured remote. Replace
the inline resolution blocks in both Push and the current function around the
branch remote lookup with calls to this helper, preserving the existing
configured-remote, branch-config, and origin fallback behavior.

998-1006: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Terminate option parsing in DeleteBranch for consistency.

Every other new helper in this file passes -- before remote or ref values. DeleteBranch passes fallbackBranch and branchToDelete directly. A branch name that starts with - would then be parsed as an option. Add -- to both commands.

🛡️ Proposed hardening
-	if _, err := gitOutput(ctx, runGit, cwd, "checkout", fallbackBranch); err != nil {
+	if _, err := gitOutput(ctx, runGit, cwd, "checkout", "--", fallbackBranch); err != nil {
 		return err
 	}
-	_, err := gitOutput(ctx, runGit, cwd, "branch", "-D", branchToDelete)
+	_, err := gitOutput(ctx, runGit, cwd, "branch", "-D", "--", branchToDelete)
 	return err

Note: git checkout -- <name> restores paths rather than switching branches. Use git switch -- <name> or validate the name instead; verify the chosen form before applying.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit.go` around lines 998 - 1006, Update DeleteBranch to
terminate git option parsing for both fallbackBranch and branchToDelete. Use an
option-safe branch-switching command that preserves switching to the fallback
branch, then pass -- before both branch values in the switch and delete
commands; do not use checkout --, which changes the command’s meaning.
internal/cli/app.go (1)

92-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Name the parameters in these function types.

Fields such as commitsAhead func(context.Context, string, string, string) (int, error) and remoteHasBranch func(context.Context, string, string, string) (bool, error) give no hint of the cwd, remote, branch order. A caller that swaps remote and branch compiles cleanly and probes the wrong ref. Add parameter names to the type, for example func(ctx context.Context, cwd, remote, branch string) (bool, error).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/app.go` around lines 92 - 105, Update the function type
declarations in the surrounding struct, especially commitsAhead and
remoteHasBranch, to name each parameter and make the cwd, remote, and branch
ordering explicit. Apply descriptive names consistently to the other context and
string parameters without changing any signatures or behavior.
internal/zerogit/zerogit_test.go (1)

1537-1559: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard the branch.autoSetupMerge=inherit dependency.

If Git versions older than 2.35 are supported, skip this test or report the required Git version before using inherit. Git 2.35 introduced this value; older versions can reject it or fail to create origin/main, causing a misleading test failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 1537 - 1559, Update
TestHasUpstreamRejectsInheritedMainUpstream to detect the installed Git version
before configuring branch.autoSetupMerge=inherit, and skip the test with a clear
required-version message when Git is older than 2.35. Perform this check before
the inherit configuration or dependent checkout, while preserving the existing
test behavior for supported Git versions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/workflow_test.go`:
- Around line 914-945: The TestExtractBranchSlug table is missing regression
coverage for punctuation-terminated suggestions and ensureFeatureBranch fallback
failures. Add a case proving “add login page.” is handled as the intended slug
rather than rejected as preamble, and add an ensureFeatureBranch test covering
both provider errors and empty generated slugs, asserting deterministic fallback
and the expected notice or logging behavior.

In `@internal/cli/workflows.go`:
- Around line 1211-1225: Update the auto-naming block around
generateAutoBranchSlug to track whether the LLM path produced a slug and print a
short fallback notice to stdout when it does not. Cover resolveConfig failures,
unavailable provider profiles, newProvider failures, generation errors, and
empty results, while preserving the existing deterministic slug fallback and
suppressing notices in jsonMode.
- Around line 1251-1258: Update the rollback handling in the resetBranchRef
failure path to capture the error returned by deleteBranch instead of discarding
it. When branch deletion also fails, include both the restore and rollback
failures in the returned error and clearly indicate that manual repair is
required; preserve the existing restore-only error when deletion succeeds or is
unavailable.

In `@internal/zerogit/zerogit_test.go`:
- Around line 1184-1194: Update the comment in the
ReturnsErrorWhenRemoteTrackingRefMissing test to state that a missing
remote-tracking ref causes CommitsAhead to return an error and callers fail
closed by refusing to proceed with the push. Remove the inaccurate “cannot tell”
and “proceeds” description while preserving the test behavior.

In `@internal/zerogit/zerogit.go`:
- Around line 783-805: Update the CreateBranch comment and DryRun behavior so
they agree: either describe DryRun as returning the requested trimmed branch
name without collision resolution, or move collision resolution before the
DryRun return so it returns the name a real run would create; preserve
non-dry-run collision handling.
- Around line 621-638: Gate the upstream verification and repair block after the
push in Push on !options.DryRun, leaving the existing UpstreamRef and branch
--set-upstream-to behavior unchanged for real pushes. Add a dry-run regression
test covering an unpublished branch that verifies no branch --set-upstream-to
command is issued and the push remains successful.

---

Nitpick comments:
In `@internal/cli/app.go`:
- Around line 92-105: Update the function type declarations in the surrounding
struct, especially commitsAhead and remoteHasBranch, to name each parameter and
make the cwd, remote, and branch ordering explicit. Apply descriptive names
consistently to the other context and string parameters without changing any
signatures or behavior.

In `@internal/cli/workflow_test.go`:
- Line 824: Update the ensureFeatureBranch call sites in workflow tests,
including the call in this test, to avoid consecutive positional boolean
arguments by introducing a named options struct or test helper for jsonMode,
allowDefaultBranch, dryRun, and autoNaming. Preserve each test’s existing flag
values while making the argument names explicit.
- Around line 2100-2120: Make TestRunChangesBareRemotePushThenPRUsable hermetic
by configuring GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to nonexistent paths in
runWorkflowGit and the CLI invocation, including the runWithDeps production
path. Explicitly disable commit signing and skip the test when the installed Git
does not support git init --bare -b main, while preserving the existing test
behavior.

In `@internal/cli/workflows.go`:
- Around line 527-540: Remove the redundant older options.message != ""
validation near the command-option checks, retaining the existing `command !=
"commit" && options.hasMessage` rule and its error message as the single
--message validation. Leave the surrounding --auto and --dry-run checks
unchanged.
- Around line 1344-1389: In extractBranchSlug, collapse firstLine and
plausibleLine into a single fallback variable because their current conditions
are equivalent, and return that variable when no slug-shaped candidate is found.
Before applying isPreambleText, trim trailing sentence punctuation from
candidates so values such as “add login page.” remain eligible for slug
extraction and generateAutoBranchSlug does not receive an empty result.
- Around line 1265-1277: Update fallbackBranchSlug to use path.Base instead of
filepath.Base when extracting the filename from summary.Files[0].Path,
preserving platform-independent handling of git’s slash-separated paths.

In `@internal/zerogit/zerogit_test.go`:
- Around line 1537-1559: Update TestHasUpstreamRejectsInheritedMainUpstream to
detect the installed Git version before configuring
branch.autoSetupMerge=inherit, and skip the test with a clear required-version
message when Git is older than 2.35. Perform this check before the inherit
configuration or dependent checkout, while preserving the existing test behavior
for supported Git versions.

In `@internal/zerogit/zerogit.go`:
- Around line 745-752: Extract the shared remote-resolution logic into a helper
such as resolveRemoteForBranch, accepting the context, Git runner, repository
root, branch, and configured remote. Replace the inline resolution blocks in
both Push and the current function around the branch remote lookup with calls to
this helper, preserving the existing configured-remote, branch-config, and
origin fallback behavior.
- Around line 998-1006: Update DeleteBranch to terminate git option parsing for
both fallbackBranch and branchToDelete. Use an option-safe branch-switching
command that preserves switching to the fallback branch, then pass -- before
both branch values in the switch and delete commands; do not use checkout --,
which changes the command’s meaning.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d5f1ebc0-9a2a-4ac7-baae-69128f1609ed

📥 Commits

Reviewing files that changed from the base of the PR and between d37de92 and 4908956.

📒 Files selected for processing (5)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/cli/workflow_test.go
Comment thread internal/cli/workflows.go Outdated
Comment thread internal/cli/workflows.go
Comment thread internal/zerogit/zerogit_test.go
Comment thread internal/zerogit/zerogit.go
Comment thread internal/zerogit/zerogit.go
Recover trailing-punctuation LLM slugs, notice deterministic fallback,
report dual restore/rollback failures, and skip Push upstream repair on
dry-run. Align comments with fail-closed and DryRun behavior.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

CodeRabbit findings addressed (173b327)

Finding Fix
Slug extraction failure coverage Strip trailing .!? so add login page. is recovered; table + e2e + fallback tests for provider error / empty slug / newProvider failure
Silent --auto fallback Print LLM branch naming unavailable; using deterministic name. (not in JSON mode)
Failed rollback discarded Include deleteBranch error in restore failure message; dual-failure test
DryRun upstream repair Gate set-upstream verification on !DryRun; regression test
Misleading comments CommitsAhead fail-closed; CreateBranch DryRun = requested name only

Verification: gofmt, go vet ./internal/cli/ ./internal/zerogit/, focused + package tests for cli/zerogit (bash completion WSL noise only on this host).

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@euxaristia have exceeded the limit for the number of chat messages per hour. Please wait 20 minutes and 24 seconds before sending another message.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 7, 2026
…mat-on-write

Use the binary path resolved by exec.LookPath and set formatter.Stdin
to an empty reader so format-on-write does not hang on inherited stdin
handles on Windows.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 8 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
internal/cli/workflows.go (5)

960-964: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document why an unborn-probe error is ignored here.

Line 961 blocks only when unbornErr == nil && unborn. A probe failure lets the flow continue to push and PR creation. That is a deliberate open-fail on a preflight whose only job is a better error message, and the later push still fails closed. The surrounding comment does not say so. Add one sentence, because every neighboring check in this PR fails closed and a reader will assume this one does too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 960 - 964, Add a concise comment
above the isUnbornRemote check explaining that probe errors are intentionally
ignored so the flow can continue, with the later push/PR operation providing the
fail-closed behavior; retain the existing condition and error handling
unchanged.

930-966: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

changes pr contacts the remote twice for the same answer.

Without --yes, Line 941 calls deps.isDefaultBranch only to learn the remote. ensureFeatureBranch then calls deps.isDefaultBranch again at Line 1111 with the same inputs. Each call runs git ls-remote --symref against the remote. The unborn probe adds a third ls-remote, and refreshTrackingRef adds a fetch. On a slow SSH remote the user waits for four sequential network round trips before anything is pushed.

Thread the first result into the preflight instead of repeating the lookup. One option: add an optional pre-resolved default-branch state parameter to ensureFeatureBranch, or move the unborn preflight inside it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 930 - 966, Avoid resolving the
default branch twice in the changes pr workflow: reuse the result from the
initial deps.isDefaultBranch call when invoking ensureFeatureBranch, or move the
unborn-remote preflight into ensureFeatureBranch. Update ensureFeatureBranch and
its callers as needed so the existing remote and branch behavior remains
unchanged while eliminating the duplicate remote lookup.

1395-1409: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

plausibleLine and firstLine are always equal, so the second branch is dead.

At Line 1399 the guard reduces to !isPreambleText(candidate): if candidate == line, the second clause already implies !isPreambleText(line); if candidate != line, the first clause is true by construction. That is the same condition as Line 1395. Both variables therefore receive the same first value, and the plausibleLine return at Line 1406 can never differ from firstLine.

Collapse the two into one variable. TestExtractBranchSlug should keep passing unchanged, which confirms the redundancy.

♻️ Proposed change
-		if firstLine == "" && !isPreambleText(candidate) {
-			firstLine = candidate
-		}
-
-		if (!isPreambleText(line) || candidate != line) && !isPreambleText(candidate) {
-			if plausibleLine == "" {
-				plausibleLine = candidate
-			}
-		}
+		if firstLine == "" && !isPreambleText(candidate) {
+			firstLine = candidate
+		}
 	}
 
-	if plausibleLine != "" {
-		return plausibleLine
-	}
 	return firstLine

Remove the plausibleLine declaration at Line 1359 as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1395 - 1409, In the branch-slug
extraction logic, remove the redundant plausibleLine variable and its assignment
condition, including its declaration. Keep a single firstLine value for the
first non-preamble candidate and return it directly, preserving the existing
behavior covered by TestExtractBranchSlug.

1106-1148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nil-guarding is inconsistent across the injected Git dependencies.

remoteHasBranch, refreshTrackingRef, isUnbornRemote, branchUpstreamRef, resetBranchRef, and deleteBranch are all nil-checked. inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and createBranch are called directly. fillAppDeps populates the second group, so production is safe, but a unit test that omits one of them panics instead of failing with a message. State the invariant in the doc comment, or guard both groups the same way.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1106 - 1148, Make dependency handling
consistent in ensureFeatureBranch and the related workflow: either document in
the relevant function comment that inspectChanges, commitsAhead,
headCommitSubject, currentGitUser, and createBranch are mandatory and must be
populated by fillAppDeps, or add nil guards matching remoteHasBranch,
refreshTrackingRef, isUnbornRemote, branchUpstreamRef, resetBranchRef, and
deleteBranch. Ensure omitted injected dependencies return a clear error instead
of panicking.

524-532: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Collapse the duplicated --message validation.

Line 524 and Line 527 return the identical error. options.message != "" is only reachable when options.hasMessage is true, so the first check is redundant. Keep the hasMessage form, which also catches --message "".

♻️ Proposed change
-	if command != "commit" && options.message != "" {
-		return options, false, execUsageError{"--message is only valid with `zero changes commit`"}
-	}
 	if command != "commit" && options.hasMessage {
 		return options, false, execUsageError{"--message is only valid with `zero changes commit`"}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 524 - 532, In the validation block of
the workflow command parser, remove the redundant options.message != "" check
and retain the options.hasMessage condition to reject --message for non-commit
commands, including an explicitly empty value. Leave the subsequent commit
--message and --auto conflict validation unchanged.
internal/zerogit/zerogit.go (1)

873-884: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Harden option handling for branch and remote names.

  • Do not add -- before the range in CommitsAhead; git rev-list treats following arguments as paths and exits with status 129. Use --end-of-options or a fully qualified remote-tracking ref.
  • In DeleteBranch, use git switch -- <fallbackBranch>. git checkout -- <fallbackBranch> checks out a path, not a branch.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit.go` around lines 873 - 884, Harden ref handling in
CommitsAhead and DeleteBranch: keep the rev-list range argument without --, but
prevent option interpretation by using --end-of-options or a fully qualified
remote-tracking ref; update DeleteBranch to switch to the fallback branch with
git switch -- <fallbackBranch> rather than checkout, preserving branch
semantics.
internal/cli/workflow_test.go (1)

2226-2245: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Isolate Git configuration for the entire test, not only runWorkflowGit.

Workflow Git commands also inherit the process environment, so global branch.autoSetupMerge can change branch creation and commit.gpgsign or core.hooksPath can break setup commits. Set the Git environment before setup and workflow execution. On Git 2.32+, use os.DevNull for GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM; otherwise use GIT_CONFIG_NOSYSTEM=1 with temporary global configuration paths. Skip Git versions older than 2.28 because git init --bare -b main is unsupported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` around lines 2226 - 2245, Isolate Git
configuration for the entire TestRunChangesBareRemotePushThenPRUsable test,
including repository setup and workflow execution, by configuring the process
environment before any Git commands run. Use os.DevNull for GIT_CONFIG_GLOBAL
and GIT_CONFIG_SYSTEM on Git 2.32+, or GIT_CONFIG_NOSYSTEM=1 with temporary
global config paths on older supported versions; skip Git versions below 2.28
before using git init --bare -b main.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tools/format_on_write.go`:
- Around line 81-90: Extend the tests for maybeFormatWrittenFile with a
formatter lookup failure case: set ZERO_FORMAT_ON_WRITE and PATH to a directory
that lacks gofmt, then assert the function returns writtenContent. Do not add a
separate nil-Stdin or EOF test.

---

Nitpick comments:
In `@internal/cli/workflow_test.go`:
- Around line 2226-2245: Isolate Git configuration for the entire
TestRunChangesBareRemotePushThenPRUsable test, including repository setup and
workflow execution, by configuring the process environment before any Git
commands run. Use os.DevNull for GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM on Git
2.32+, or GIT_CONFIG_NOSYSTEM=1 with temporary global config paths on older
supported versions; skip Git versions below 2.28 before using git init --bare -b
main.

In `@internal/cli/workflows.go`:
- Around line 960-964: Add a concise comment above the isUnbornRemote check
explaining that probe errors are intentionally ignored so the flow can continue,
with the later push/PR operation providing the fail-closed behavior; retain the
existing condition and error handling unchanged.
- Around line 930-966: Avoid resolving the default branch twice in the changes
pr workflow: reuse the result from the initial deps.isDefaultBranch call when
invoking ensureFeatureBranch, or move the unborn-remote preflight into
ensureFeatureBranch. Update ensureFeatureBranch and its callers as needed so the
existing remote and branch behavior remains unchanged while eliminating the
duplicate remote lookup.
- Around line 1395-1409: In the branch-slug extraction logic, remove the
redundant plausibleLine variable and its assignment condition, including its
declaration. Keep a single firstLine value for the first non-preamble candidate
and return it directly, preserving the existing behavior covered by
TestExtractBranchSlug.
- Around line 1106-1148: Make dependency handling consistent in
ensureFeatureBranch and the related workflow: either document in the relevant
function comment that inspectChanges, commitsAhead, headCommitSubject,
currentGitUser, and createBranch are mandatory and must be populated by
fillAppDeps, or add nil guards matching remoteHasBranch, refreshTrackingRef,
isUnbornRemote, branchUpstreamRef, resetBranchRef, and deleteBranch. Ensure
omitted injected dependencies return a clear error instead of panicking.
- Around line 524-532: In the validation block of the workflow command parser,
remove the redundant options.message != "" check and retain the
options.hasMessage condition to reject --message for non-commit commands,
including an explicitly empty value. Leave the subsequent commit --message and
--auto conflict validation unchanged.

In `@internal/zerogit/zerogit.go`:
- Around line 873-884: Harden ref handling in CommitsAhead and DeleteBranch:
keep the rev-list range argument without --, but prevent option interpretation
by using --end-of-options or a fully qualified remote-tracking ref; update
DeleteBranch to switch to the fallback branch with git switch --
<fallbackBranch> rather than checkout, preserving branch semantics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c147ff2e-ff04-4b0e-a705-a31fd831471c

📥 Commits

Reviewing files that changed from the base of the PR and between d37de92 and 76d6027.

📒 Files selected for processing (6)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/tools/format_on_write.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/tools/format_on_write.go
Remove redundant command validation, eliminate duplicate remote lookup in PR
workflow, harden ref options and branch switching, isolate test Git
environment, and add formatter lookup test.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
internal/cli/workflows.go (1)

1091-1096: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the truncated sentence in the doc comment.

Line 1091 begins "The working tree must be clean and HEAD must be ahead of the resolved remote" and never ends. Line 1092 starts a separate sentence with the function name. Two drafts appear to have been merged.

📝 Proposed fix
-// The working tree must be clean and HEAD must be ahead of the resolved remote
-// ensureFeatureBranch verifies working tree cleanliness, checks default branch
-// state, and auto-creates a feature branch if on the default branch. The
-// inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and
-// createBranch fields on deps are mandatory dependencies populated by
-// fillAppDeps.
+// The working tree must be clean and HEAD must be ahead of the resolved
+// remote branch; both are verified before any branch is created. The
+// inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and
+// createBranch fields on deps are mandatory dependencies populated by
+// fillAppDeps.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1091 - 1096, Complete the opening
sentence of the doc comment before the “ensureFeatureBranch” sentence,
preserving the intended requirements that the working tree is clean and HEAD is
ahead of the resolved remote. Keep the existing dependency documentation
unchanged.
internal/zerogit/zerogit.go (1)

837-850: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Terminate option parsing on checkout -b and validate Name.

Every other new remote-facing call in this file uses -- and rejects option-shaped values. CreateBranch does not. options.Name is only trimmed. An exported caller that passes a name beginning with - makes Git parse it as an option. The current in-repo caller passes zerogit.BuildBranchName output, which is safe, so this is hardening at the API boundary rather than a live defect.

ResetBranchRef already validates its branch argument. Apply the same discipline here.

🛡️ Proposed hardening
 	name := strings.TrimSpace(options.Name)
 	if name == "" {
 		return BranchResult{}, fmt.Errorf("branch name required")
 	}
+	// refs/heads/<name> must stay inside the heads namespace, and the name
+	// must never reach `checkout` as an option.
+	if strings.HasPrefix(name, "-") || strings.HasPrefix(name, "/") ||
+		strings.Contains(name, "..") || strings.ContainsAny(name, "\\ \t\n") {
+		return BranchResult{}, fmt.Errorf("invalid branch name %q", name)
+	}
-	if _, err := gitOutput(ctx, runGit, root, "checkout", "-b", name); err != nil {
+	if _, err := gitOutput(ctx, runGit, root, "checkout", "-b", name, "--"); err != nil {
 		return BranchResult{}, fmt.Errorf("create branch %q: %w", name, err)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit.go` around lines 837 - 850, Harden CreateBranch by
validating options.Name with the same branch-name validation used by
ResetBranchRef, rejecting option-shaped or otherwise invalid names before Git
commands run. Update the checkout -b invocation to terminate option parsing with
-- while preserving the existing collision and branch-creation behavior.
internal/cli/app.go (1)

99-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused branchHasUpstream dependency wiring.

appDeps.branchHasUpstream is only declared, wired in defaultAppDeps, and defaulted in fillAppDeps; it is never called. Remove the field and its wiring if the feature branch flow only needs remoteHasBranch; keep zerogit.HasUpstream and its tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/app.go` at line 99, Remove the unused appDeps.branchHasUpstream
field and its wiring from defaultAppDeps and fillAppDeps, provided the feature
branch flow relies only on remoteHasBranch. Preserve zerogit.HasUpstream and its
existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/workflows.go`:
- Around line 952-961: Remove the unreachable `if err != nil` check following
the `isUnbornRemote` probe in the workflow, since `ensureFeatureBranch` errors
are already handled earlier. Preserve the intentional `unbornErr` discard and
the existing unborn-remote and successful-flow behavior.

In `@internal/zerogit/zerogit_test.go`:
- Around line 1525-1618: Isolate Git configuration in
TestRemoteHasBranchSeesPushWithoutLocalUpstream and
TestHasUpstreamRejectsInheritedMainUpstream by adding the same three t.Setenv
calls used by TestRunChangesBareRemotePushThenPRUsable, covering
push.autoSetupRemote and branch.autoSetupMerge with platform-safe environment
handling. Ensure os is imported if needed, before either test invokes git.

---

Nitpick comments:
In `@internal/cli/app.go`:
- Line 99: Remove the unused appDeps.branchHasUpstream field and its wiring from
defaultAppDeps and fillAppDeps, provided the feature branch flow relies only on
remoteHasBranch. Preserve zerogit.HasUpstream and its existing tests.

In `@internal/cli/workflows.go`:
- Around line 1091-1096: Complete the opening sentence of the doc comment before
the “ensureFeatureBranch” sentence, preserving the intended requirements that
the working tree is clean and HEAD is ahead of the resolved remote. Keep the
existing dependency documentation unchanged.

In `@internal/zerogit/zerogit.go`:
- Around line 837-850: Harden CreateBranch by validating options.Name with the
same branch-name validation used by ResetBranchRef, rejecting option-shaped or
otherwise invalid names before Git commands run. Update the checkout -b
invocation to terminate option parsing with -- while preserving the existing
collision and branch-creation behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e5861a6-53ab-44c7-8e86-9489e0c55078

📥 Commits

Reviewing files that changed from the base of the PR and between d37de92 and cffba94.

📒 Files selected for processing (7)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/tools/format_on_write.go
  • internal/tools/format_on_write_test.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/cli/workflows.go Outdated
Comment thread internal/zerogit/zerogit_test.go
Harden CreateBranch name validation, remove unreachable workflow error check,
complete doc comment, isolate test Git environment, and clean up unused
appDeps field.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
internal/cli/workflow_test.go (2)

2325-2341: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Bind runWorkflowGit to the test deadline.

runGitCommand in internal/zerogit/zerogit_test.go derives a context from t.Deadline() before running git. runWorkflowGit uses a plain exec.Command. If a git subprocess hangs against the bare remote, this test blocks until the whole test binary times out, with no indication of which command stalled.

🧪 Proposed change
 func runWorkflowGit(t *testing.T, dir string, args ...string) string {
 	t.Helper()
-	cmd := exec.Command("git", args...)
+	ctx := context.Background()
+	if deadline, ok := t.Deadline(); ok {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithDeadline(ctx, deadline)
+		defer cancel()
+	}
+	cmd := exec.CommandContext(ctx, "git", args...)
 	cmd.Dir = dir
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` around lines 2325 - 2341, Update
runWorkflowGit to derive a context from t.Deadline() and run the git subprocess
with exec.CommandContext instead of exec.Command, preserving the existing
command arguments, working directory, and failure reporting.

828-828: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider an options struct for ensureFeatureBranch.

Every call site passes nine positional arguments, including four consecutive bools: jsonMode, allowDefaultBranch, dryRun, autoNaming. This file repeats that shape about twenty-five times. Transposing two of the bools compiles cleanly and silently changes which guard the test exercises, so a broken test can still pass for the wrong reason.

Grouping the flags into a small options struct would make each call site self-documenting and make the bool order impossible to get wrong.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflow_test.go` at line 828, Introduce a small options struct
for ensureFeatureBranch containing jsonMode, allowDefaultBranch, dryRun, and
autoNaming, then update ensureFeatureBranch and all call sites in this test file
to pass the struct instead of positional boolean arguments. Preserve existing
behavior and values while making each option explicitly named.
internal/cli/app.go (1)

640-641: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the restore opt-out explicit instead of relying on nil defaults.

fillAppDeps leaves deleteBranch and resetBranchRef nil so unit tests do not touch a real git tree. Production is correct today because Run passes defaultAppDeps(). The fragility is that ensureFeatureBranch silently skips the default-branch restore when resetBranchRef is nil. Any future caller that builds a partial appDeps loses a correctness behavior with no signal: local main keeps the pushed commits and diverges after a squash-merge.

Consider filling both from defaults like every other dependency and having tests inject explicit no-op stubs. That keeps the skip intentional and visible at the call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/app.go` around lines 640 - 641, Update fillAppDeps to initialize
deleteBranch and resetBranchRef from defaultAppDeps rather than leaving them
nil, and adjust affected unit tests to inject explicit no-op stubs when real git
restoration or deletion should be skipped. Preserve ensureFeatureBranch’s
restore behavior for production and make test opt-outs explicit at their appDeps
construction sites.
internal/zerogit/zerogit_test.go (2)

763-789: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the residual-upstream-mismatch branch.

SurfacesUpstreamWriteFailureWhenRecoveryFails covers the set-upstream-to command failing. The second guard in Push is not covered: set-upstream-to exits 0 but UpstreamRef still does not return <remote>/<branch>, which produces the "local upstream is still not" error. Add a fixture where the repair command succeeds and the follow-up UpstreamRef returns a different value.

🧪 Proposed additional subtest
t.Run("SurfacesUpstreamStillWrongAfterRepair", func(t *testing.T) {
	root := t.TempDir()
	runner := &fakeRunner{results: []CommandResult{
		{Stdout: root + "\n"},
		{Stdout: "user/slug\n"},
		{Stdout: "origin\n"},
		{Stdout: "ref: refs/heads/main\tHEAD\nabc123\tHEAD\n"},
		{Stdout: "To origin\n * [new branch] user/slug -> user/slug\n"},
		{ExitCode: 128, Stderr: "fatal: no upstream configured"},
		{Stdout: ""},              // branch --set-upstream-to succeeds
		{Stdout: "origin/main\n"}, // but the upstream is still wrong
	}}

	_, err := Push(context.Background(), PushOptions{Cwd: root, RunGit: runner.Run})
	if err == nil || !strings.Contains(err.Error(), "local upstream is still not") {
		t.Fatalf("expected residual-mismatch error, got %v", err)
	}
})

As per coding guidelines: "Every behavior or security-boundary change requires a regression test, including failure paths".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 763 - 789, Add a regression
subtest alongside SurfacesUpstreamWriteFailureWhenRecoveryFails that exercises
successful set-upstream-to recovery followed by UpstreamRef returning an
incorrect remote/branch. Configure fakeRunner with the full command sequence,
assert Push returns an error containing “local upstream is still not,” and
preserve the existing test’s coverage for repair-command failure.

Source: Coding guidelines


1461-1523: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider isolating ambient Git config in the initGitRepo-based tests too.

TestRemoteHasBranchSeesPushWithoutLocalUpstream and TestHasUpstreamRejectsInheritedMainUpstream pin GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM, and GIT_CONFIG_NOSYSTEM. The three tests here run the real git binary through initGitRepo without that isolation. The risk is lower because branch -M main normalizes the branch name and no push occurs, so this is not blocking. Setting the same three variables inside initGitRepo would make every real-git test in this file hermetic in one place.

As per coding guidelines: "Code and tests must pass on Linux, macOS, and Windows".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit_test.go` around lines 1461 - 1523, Update
initGitRepo, or the shared setup it uses for real-git tests, to isolate ambient
Git configuration by setting GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to
controlled test values and GIT_CONFIG_NOSYSTEM appropriately. Apply this
centrally so TestResetBranchRefMovesDefaultWithoutTouchingFeature,
TestResetBranchRefRefusesCheckedOutBranch, and
TestCurrentBranchReturnsCheckedOutName inherit the same hermetic setup, while
preserving cross-platform behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/workflows.go`:
- Around line 868-871: Bound the feature-branch preflight context before calling
ensureFeatureBranch in runChangesPush and runChangesPR, replacing
context.Background() with a deadline-bearing context suitable for the blocking
git and remote operations. Apply the change at internal/cli/workflows.go lines
868-871 and 927-930, preserving existing cancellation and error handling.

In `@internal/tools/format_on_write_test.go`:
- Around line 84-90: Update TestFormatOnWriteFormatterLookupFailure to write the
expected Go source content to the a.go path before calling
maybeFormatWrittenFile. Store that content in a variable and compare the
returned value against the same variable, ensuring the test specifically
exercises formatter lookup failure rather than a nonexistent-file execution
failure.

---

Nitpick comments:
In `@internal/cli/app.go`:
- Around line 640-641: Update fillAppDeps to initialize deleteBranch and
resetBranchRef from defaultAppDeps rather than leaving them nil, and adjust
affected unit tests to inject explicit no-op stubs when real git restoration or
deletion should be skipped. Preserve ensureFeatureBranch’s restore behavior for
production and make test opt-outs explicit at their appDeps construction sites.

In `@internal/cli/workflow_test.go`:
- Around line 2325-2341: Update runWorkflowGit to derive a context from
t.Deadline() and run the git subprocess with exec.CommandContext instead of
exec.Command, preserving the existing command arguments, working directory, and
failure reporting.
- Line 828: Introduce a small options struct for ensureFeatureBranch containing
jsonMode, allowDefaultBranch, dryRun, and autoNaming, then update
ensureFeatureBranch and all call sites in this test file to pass the struct
instead of positional boolean arguments. Preserve existing behavior and values
while making each option explicitly named.

In `@internal/zerogit/zerogit_test.go`:
- Around line 763-789: Add a regression subtest alongside
SurfacesUpstreamWriteFailureWhenRecoveryFails that exercises successful
set-upstream-to recovery followed by UpstreamRef returning an incorrect
remote/branch. Configure fakeRunner with the full command sequence, assert Push
returns an error containing “local upstream is still not,” and preserve the
existing test’s coverage for repair-command failure.
- Around line 1461-1523: Update initGitRepo, or the shared setup it uses for
real-git tests, to isolate ambient Git configuration by setting
GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM to controlled test values and
GIT_CONFIG_NOSYSTEM appropriately. Apply this centrally so
TestResetBranchRefMovesDefaultWithoutTouchingFeature,
TestResetBranchRefRefusesCheckedOutBranch, and
TestCurrentBranchReturnsCheckedOutName inherit the same hermetic setup, while
preserving cross-platform behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c88a954-d4ed-4b5a-8174-771fcbdfc612

📥 Commits

Reviewing files that changed from the base of the PR and between d37de92 and 963796c.

📒 Files selected for processing (7)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/tools/format_on_write.go
  • internal/tools/format_on_write_test.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/cli/workflows.go Outdated
Comment thread internal/tools/format_on_write_test.go
…ings on PR 671

Use featureBranchOptions struct for ensureFeatureBranch, bound preflight
contexts, add upstream repair mismatch subtest, and centralize test Git
config isolation.

Refs Gitlawb#671
@euxaristia

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/zerogit/zerogit.go (1)

748-755: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared remote-resolution logic.

Push (lines 581-588) and IsDefaultBranch resolve the remote with identical code: explicit option, then branch.<name>.remote, then "origin". The CLI threads the remote from IsDefaultBranch into Push, so the two must stay identical. A single helper removes the risk of divergence.

♻️ Proposed helper
func resolveRemoteForBranch(ctx context.Context, runGit Runner, root, requested, branch string) string {
	if remote := strings.TrimSpace(requested); remote != "" {
		return remote
	}
	if upstream, err := gitOutput(ctx, runGit, root, "config", "branch."+branch+".remote"); err == nil && upstream != "" {
		return upstream
	}
	return "origin"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/zerogit/zerogit.go` around lines 748 - 755, Extract the duplicated
remote-selection logic into a shared resolveRemoteForBranch helper, preserving
the order of explicit trimmed remote, branch.<name>.remote configuration, and
"origin" fallback. Update both Push and IsDefaultBranch to use this helper so
their remote resolution remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/workflows.go`:
- Around line 1106-1117: Complete the truncated dependency-contract comment by
stating that inspectChanges, commitsAhead, headCommitSubject, currentGitUser,
and createBranch are required non-nil dependencies, while the remaining listed
dependencies may be nil and are guarded. Move the entire comment block from
featureBranchOptions to directly above ensureFeatureBranch so its documentation
applies to the function.

---

Nitpick comments:
In `@internal/zerogit/zerogit.go`:
- Around line 748-755: Extract the duplicated remote-selection logic into a
shared resolveRemoteForBranch helper, preserving the order of explicit trimmed
remote, branch.<name>.remote configuration, and "origin" fallback. Update both
Push and IsDefaultBranch to use this helper so their remote resolution remains
identical.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d32f90b9-2e17-47c6-afed-de78d21919fe

📥 Commits

Reviewing files that changed from the base of the PR and between d37de92 and 40e62d8.

📒 Files selected for processing (7)
  • internal/cli/app.go
  • internal/cli/workflow_test.go
  • internal/cli/workflows.go
  • internal/tools/format_on_write.go
  • internal/tools/format_on_write_test.go
  • internal/zerogit/zerogit.go
  • internal/zerogit/zerogit_test.go

Comment thread internal/cli/workflows.go
Comment on lines +1106 to +1117
// The working tree must be clean and HEAD must be ahead of the resolved
// remote branch; both are verified before any branch is created. The
// inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and
type featureBranchOptions struct {
JSONMode bool
AllowDefaultBranch bool
DryRun bool
AutoNaming bool
MaxDiffBytes int
}

func ensureFeatureBranch(ctx context.Context, stdout io.Writer, workspaceRoot string, requestedRemote string, opts featureBranchOptions, deps appDeps) (string, string, bool, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Finish the truncated doc sentence and move it onto ensureFeatureBranch.

Line 1108 ends mid-sentence: "The inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and". The reader never learns the contract for those dependencies. That contract matters: ensureFeatureBranch calls inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and createBranch without nil guards, while it does guard isDefaultBranch, remoteHasBranch, refreshTrackingRef, isUnbornRemote, branchUpstreamRef, resetBranchRef, and deleteBranch. A nil value in the first group panics.

The block also documents a function but sits on the featureBranchOptions type declaration, so go doc featureBranchOptions prints function prose.

Complete the sentence and move the block below the type, directly above func ensureFeatureBranch.

As per coding guidelines: "Ensure PR descriptions, help text, and comments match shipped behavior".

📝 Proposed fix
-// The working tree must be clean and HEAD must be ahead of the resolved
-// remote branch; both are verified before any branch is created. The
-// inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and
 type featureBranchOptions struct {
 	JSONMode           bool
 	AllowDefaultBranch bool
 	DryRun             bool
 	AutoNaming         bool
 	MaxDiffBytes       int
 }
 
+// (…existing doc block moves here…)
+// The working tree must be clean and HEAD must be ahead of the resolved
+// remote branch; both are verified before any branch is created. The
+// inspectChanges, commitsAhead, headCommitSubject, currentGitUser, and
+// createBranch dependencies are required on the default-branch path and are
+// called without nil guards; fillAppDeps populates all of them. The
+// refreshTrackingRef, isUnbornRemote, branchUpstreamRef, resetBranchRef, and
+// deleteBranch dependencies are optional and are skipped when nil.
 func ensureFeatureBranch(ctx context.Context, stdout io.Writer, workspaceRoot string, requestedRemote string, opts featureBranchOptions, deps appDeps) (string, string, bool, error) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/workflows.go` around lines 1106 - 1117, Complete the truncated
dependency-contract comment by stating that inspectChanges, commitsAhead,
headCommitSubject, currentGitUser, and createBranch are required non-nil
dependencies, while the remaining listed dependencies may be nil and are
guarded. Move the entire comment block from featureBranchOptions to directly
above ensureFeatureBranch so its documentation applies to the function.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants